package com.wbs.shop.nuby.api;


import com.wbs.shop.nuby.api.exception.FirmException;
import com.wbs.shop.nuby.api.exception.NullDataException;
import com.wbs.shop.nuby.api.exception.RetryException;
import com.wbs.shop.nuby.bean.ResultBean;
import com.wbs.shop.nuby.view.base.LifecycleEvent;
import com.wbs.shop.nuby.view.base.LifecycleProvider;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;

import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;

/*
 * Created by zhoucl on 2017/9/16.
 */

public class RxHelper {
    private static final int RETRY_DELAY_MILLIS = 2000;
    private static final int MAX_RETRIES = 3;

    public static class Builder {
        private boolean ioMain = true;
        private boolean rxLifecycle = false;
        private WeakReference<LifecycleProvider> lifecycleProvider;
        private LifecycleEvent lifecycleEvent;
        private boolean retryWhen = false;
        private int maxRetries = MAX_RETRIES;
        private int retryDelayMillis = RETRY_DELAY_MILLIS;

        public Builder ioMain(boolean ioMain) {
            this.ioMain = ioMain;
            return this;
        }

        public Builder lifecycleProvider(LifecycleProvider lifecycleProvider) {
            this.rxLifecycle = true;
            this.lifecycleProvider = new WeakReference<>(lifecycleProvider);
            this.lifecycleEvent = LifecycleEvent.DESTROY;
            return this;
        }

        public Builder retryWhen(int maxRetries, int retryDelayMillis) {
            this.retryWhen = true;
            this.maxRetries = maxRetries;
            this.retryDelayMillis = retryDelayMillis;
            return this;
        }

        public <T> ObservableTransformer<ResultBean<T>, T> create() {
            return tObservable -> {
                Observable<T> observable = tObservable.flatMap(new HandleResultFunction<>());
                return doObservable(observable);
            };
        }

        public <T> ObservableTransformer<T, T> createNotHandleResult() {
            return this::doObservable;
        }

        private <T> Observable<T> doObservable(Observable<T> tObservable) {
            if (rxLifecycle)
                tObservable = tObservable.takeUntil(lifecycleProvider.get().getLifeSubject().filter(event -> event.equals(lifecycleEvent)));
            if (retryWhen)
                tObservable = tObservable.retryWhen(new RetryWithDelay(maxRetries, retryDelayMillis));
            if (ioMain)
                tObservable = tObservable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
            return tObservable;
        }
    }

    public static <T> ObservableTransformer<ResultBean<T>, T> handleResult(LifecycleProvider lifecycleProvider) {
        return new Builder().lifecycleProvider(lifecycleProvider).create();
    }

    public static <T> ObservableTransformer<ResultBean<T>, T> handleResult() {
        return new Builder().create();
    }

    public static <T> ObservableTransformer<ResultBean<T>, T> onlyHandleResult() {
        return new Builder().ioMain(false).create();
    }

    public static <T> ObservableTransformer<T, T> lifecycleProvider(LifecycleProvider lifecycleProvider) {
        return new Builder().lifecycleProvider(lifecycleProvider).createNotHandleResult();
    }

    public static <T> ObservableTransformer<T, T> ioMain() {
        return new Builder().createNotHandleResult();
    }

    public static <T> ObservableTransformer<T, T> ioMain(LifecycleProvider lifecycleProvider) {
        return new Builder().lifecycleProvider(lifecycleProvider).createNotHandleResult();
    }

    public static <T> ObservableTransformer<T, T> retryWhen(int maxRetries, int retryDelayMillis) {
        return new RxHelper.Builder().ioMain(false).retryWhen(maxRetries, retryDelayMillis).createNotHandleResult();
    }

    private static class HandleResultFunction<T> implements Function<ResultBean<T>, Observable<? extends T>> {

        @Override
        public Observable<? extends T> apply(@NonNull ResultBean<T> httpResult) throws Exception {
            if (httpResult.isOk()) {
                T data = httpResult.getData();
                if (data == null) {
                    return Observable.error(new NullDataException());
                } else {
                    return Observable.just(data);
                }
            } else {
                return Observable.error(new FirmException(httpResult.getMsg(), httpResult.getCode(), httpResult.getData()));
            }
        }
    }

    private static class RetryWithDelay implements Function<Observable<Throwable>, ObservableSource<?>> {

        private final int maxRetries;
        private final int retryDelayMillis;
        private int retryCount;

        public RetryWithDelay(int maxRetries, int retryDelayMillis) {
            this.maxRetries = maxRetries;
            this.retryDelayMillis = retryDelayMillis;
        }


        @Override
        public ObservableSource<?> apply(@NonNull Observable<Throwable> observable) throws Exception {
            return observable.flatMap(throwable -> {
                if (throwable instanceof UnknownHostException ||
                        throwable instanceof IOException ||
                        throwable instanceof FirmException) {
                    return Observable.error(throwable);
                }
                if (throwable instanceof RetryException && ++retryCount <= maxRetries) {
                    return Observable.timer(retryDelayMillis, TimeUnit.MILLISECONDS);
                }
                return Observable.error(throwable);
            });
        }
    }
}
